import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Point;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;

public class DrawObject
{
    // DEFAULT DrawObject abilities:
    public boolean IsJavaCADU()
    {
        return false;
    }

    public boolean IsLearningNeuron()
    {
        return IsInLearningNeuronState;
    }

    public boolean IsLine()
    {
        return false;
    }

    public boolean IsNeuron()
    {
        return false;
    }

    public boolean IsNeuronInputField()
    {
        return false;
    }

    public boolean IsSampleAndHold()
    {
        return false;
    }

    public boolean IsTextField()
    {
        return false;
    }

    public boolean IsVisualField()
    {
        return false;
    }

    public boolean IsVisualFieldPixel()
    {
        return false;
    }

    public boolean CanBeDeletedByUser()
    {
        return false;
    }

    public boolean CanBeExcitedByUser()
    {
        return false;
    }

    public boolean CanBeMovedByUser()
    {
        return false;
    }

    public boolean CanBeSelectedByUser()
    {
        return false;
    }
    // end of DEFAULT DrawObject abilities

    private static final short FileFormatVersion = 4; // v1.0 Beta 3: 3=>4

    public static final long ID_UNSET = -1; // ATTENTION: code assumes it's < 0; use when any kind of id has not been assigned

    public static final String TextFieldDefaultText = "?";

    private static final Font DrawStringDefaultFont = new Font("Monospaced", Font.PLAIN, 18);
    private static final Font DrawStringSmallFont = new Font("Monospaced", Font.PLAIN, 14);
    private static final Font DrawStringHugeFont = new Font("Arial", Font.PLAIN, 80);

    private static final Color TransparentColorBlack = new Color(0, 0, 0, 64);

    private static long CompoundIdNext = 0;	// Convention: we ALWAYS use long values for Ids, we do hardly need more space but can avoid (Integer) overflows!

    private static boolean ReducedDrawOrderEnabled = false;

    public static final long PROPERTY_DONT_DRAW_IN_REDUCED_DRAWING_MODE = (1L << 0);

    protected long Properties = 0;
    protected Point Pos = new Point(0, 0);
    protected Point Size = new Point(0, 0);
    protected boolean IsHighlighted = false;
    protected boolean IsZombie = false;
    protected boolean IsPartOfMultiSelection = false;
    protected boolean IsCommentField = false;

    // properties that apply to certain class types only, we assign them
    // to all classes to be able to easily save/load state with one
    // method (some memory wasted, but negligible - some few class types
    // must still override save/load state methods):

    // e.g. Neuron/NeuronInputNode/NeuronInputField/NeuronOutputNode:
    protected long CompoundId = ID_UNSET; // could be the same among SEVERAL files (but unique in one and the same file), pay attention when loading several files!

    // TextField specific:
    protected String Text = TextFieldDefaultText;

    // Neuron specific:
    protected int StrengthMax = 100; // means percent, don't use floating point values to avoid rounding errors (see ExcitementTriggerValue)
    protected int Strength = 0;
    protected int StrengthTrigger = 0;
    protected int StrengthIncreaseStep = 5; // add 5 percent, not too much for faster increasing, not too few to avoid we can't demand e.g. exactly 3 fields to be activated at once, because of the division reminder
    protected boolean IsNegative = false;
    protected boolean IsInLearningNeuronState = false;

    protected final static ArrayList<String> EmptyHormoneNames = new ArrayList<String>(); // save memory by always referring to the same empty collection
    protected final static ArrayList<Double> EmptyHormoneStrengths = new ArrayList<Double>(); // save memory by always referring to the same empty collection

    protected ArrayList<String> HormoneNames = EmptyHormoneNames; // save memory by always referring to the same empty collection
    protected ArrayList<Double> HormoneStrengths = EmptyHormoneStrengths; // save memory by always referring to the same empty collection

    // VisualFieldPixel specific:
    protected boolean IsEnabled;

    // states not saved to file:
    protected double ExcitementCurrent;
    protected double ExcitementAddForNextTick; // temporary
    protected double ExcitementLossPerTick;
    protected double ExcitementTriggerValue = 0.99; // use "easy" value to easily understand how to set Strengths; don't use exactly 1.0, there seem to be rounding errors in floating point calculation (tested)

    protected DrawObject ExcitementPartner = null;
    protected boolean ExcitementPartnerServedInThisTick = false;
    protected double ExcitementPartnerExcitement;

    protected final int ExcitementOnceCorrectionFactor = 2; // HACK!!! Without, activation is not visible on GUI (probably because redrawing runs in own Java thread) - if required, please search code for further usages of ExcitementOnceCorrectionFactor (e.g. in Neuron)
    protected final int ExcitementOnceMilliseconds = (1000 / Simulation.GetTicksPerSecond()) * ExcitementOnceCorrectionFactor; // more than one tick necessary, probably because of delayed GUI redrawing
    protected final int ExcitementNeuronInputNodeMilliseconds = 1000;
    protected final int ExcitementVisualFieldPixelMilliseconds = 2000;

    protected int ExcitementHistoryLength = 1; // hack for drawing excitement bar correctly, the problem is that Java's repaint() runs in an own thread (https://en.wikipedia.org/wiki/Event_dispatching_thread) and redrawing seems to have been done when some excitement has already been lost
    protected double ExcitementHistory[] = new double[ExcitementHistoryLength]; // determine max excitement during the last ExcitementHistoryLength ticks
    protected int ExcitementHistoryWriteIndex = 0;

    // parent stuff
    protected DrawObject ParentDrawObject = null;
    // ^
    // NOTE: ChildDrawObjects are DrawObjects tied to a ParentDrawObject.
    // When any ChildDrawObject is to be moved, it doesn't apply instantly but
    // tells his ParentDrawObject to move. The parent moves itself and calls
    // ChildDrawObject.MoveThis(), which does not move its parent any more.
    // Similar applies (sometimes) for highlighting and other DrawObject actions.
    // This is required as the user could e.g. select multiple, but not all,
    // VisualFieldPixels, but ALL pixels AND the VisualField itself should to be moved.
    // A problem is that moving MULTIPLE VisualFieldPixels will lead to calling
    // VisualField.Move() SEVERAL times, so that the whole VisualField with its
    // pixels could "fly away" as being moved multiple times by the same MoveAmount.

    public static Point AlignOnGrid(Point P)
    {
        Point POnGrid = new Point(P);

        // NOTE: don't touch this... just % did not work, problems if
        // value negative (tested)

        int GridSizex = 50;
        int GridSizey = 50;

        if (POnGrid.x < 0)
        {
            POnGrid.x -= GridSizex / 2;
            POnGrid.x = -POnGrid.x;
            POnGrid.x = POnGrid.x - (int) (POnGrid.x % 50);
            POnGrid.x = -POnGrid.x;
        }
        else
        {
            POnGrid.x += GridSizex / 2;
            POnGrid.x = POnGrid.x - (int) (POnGrid.x % 50);
        }

        if (POnGrid.y < 0)
        {
            POnGrid.y -= GridSizey / 2;
            POnGrid.y = -POnGrid.y;
            POnGrid.y = POnGrid.y - (int) (POnGrid.y % 50);
            POnGrid.y = -POnGrid.y;
        }
        else
        {
            POnGrid.y += GridSizey / 2;
            POnGrid.y = POnGrid.y - (int) (POnGrid.y % 50);
        }

        return POnGrid;
    }

    public static long GetNewCompoundId()
    {
        long CompoundIdNew = CompoundIdNext;
        CompoundIdNext++;
        return CompoundIdNew;
    };

    public static void SetMinimalCompoundId(long CompoundIdNextNew) // use when loading a SubSystem, just pass any minimal number that cannot be in use as CompoundId yet
    {
        if (CompoundIdNextNew >= 0)
            CompoundIdNext = CompoundIdNextNew; // CompoundIdNext is static and thus is unique as long as the program is executed
        else
            System.out.println("error in DrawObject.SetMinimalCompoundId(): passed value not useful!");
    };

    public static void SetReducedDrawOrderEnabled(boolean ReducedDrawOrderEnabledNew)
    {
        ReducedDrawOrderEnabled = ReducedDrawOrderEnabledNew;
    };

    static Point UnZoomPoint(Point p)
    {
        return new Point(UnZoomPointX(p.x), UnZoomPointY(p.y));
    };

    static int UnZoomPointX(int x)
    {
        return (int) ((x - ZoomManager.GetZoomShiftX()) / ZoomManager.GetZoomFactor());
    };

    static int UnZoomPointY(int y)
    {
        return (int) ((y - ZoomManager.GetZoomShiftY()) / ZoomManager.GetZoomFactor());
    };

    static Point ZoomAmount(Point p)
    {
        return new Point(ZoomAmountX(p.x), ZoomAmountX(p.y));
    };

    static int ZoomAmountX(int x)
    {
        return (int) (x / ZoomManager.GetZoomFactor());
    };

    static int ZoomAmountY(int y)
    {
        return (int) (y / ZoomManager.GetZoomFactor());
    }

    static Point ZoomPoint(Point p)
    {
        return new Point(ZoomPointX(p.x), ZoomPointY(p.y));
    }

    static int ZoomPointX(int x)
    {
        return (int) (x * ZoomManager.GetZoomFactor() + ZoomManager.GetZoomShiftX());
    }

    static int ZoomPointY(int y)
    {
        return (int) (y * ZoomManager.GetZoomFactor() + ZoomManager.GetZoomShiftY());
    }

    protected void AddExcitementForCurrentTick(double ExcitementAdd)
    {
        // limit new excitement value to avoid that multiple adds lead to such a high value that loss has no effect any more
        ExcitementCurrent = LimitExcitement(ExcitementCurrent + ExcitementAdd);
    }

    protected void AddExcitementForNextTick(double ExcitementAdd)
    {
        // limit new excitement value to avoid that multiple adds lead to such a high value that loss has no effect any more
        ExcitementAddForNextTick = LimitExcitement(ExcitementAddForNextTick + ExcitementAdd);
    }

    public void AddProperty(long PropertyBitMask)
    {
        Properties |= PropertyBitMask;
    }

    protected void AfterCollectingAllExcitementForNextTick()
    {
        // you may @Override this
    }

    protected boolean ContainsPoint(Point PointPos)
    {
        if (PointPos.x >= Pos.x && PointPos.x < Pos.x + Size.x)
            if (PointPos.y >= Pos.y && PointPos.y < Pos.y + Size.y)
                return true;

        return false;
    }

    protected boolean DoesActivate(double ExcitementToTest)
    {
        return (ExcitementToTest >= ExcitementTriggerValue); // user greater EQUAL!
    }

    protected void Draw(Graphics g)
    {
        // you NEED to @Override this!
    }

    protected void DrawFinal(Graphics g)
    {
        // you CAN @Override this!
    }

    protected void DrawLine(Graphics g, int x1, int y1, int x2, int y2)
    {
        if (ZoomManager.GetZoomFactor() != 1.0)
        {
            x1 = ZoomPointX(x1);
            y1 = ZoomPointY(y1);
            x2 = ZoomPointX(x2);
            y2 = ZoomPointY(y2);
        }
        g.drawLine(x1, y1, x2, y2);
    }

    protected void DrawRect(Graphics g, int x, int y, int width, int height)
    {
        if (ZoomManager.GetZoomFactor() != 1.0)
        {
            x = ZoomPointX(x);
            y = ZoomPointY(y);
            width = (int) (width * ZoomManager.GetZoomFactor());
            height = (int) (height * ZoomManager.GetZoomFactor());
        }
        g.drawRect(x, y, width, height);
    }

    protected void DrawString(Graphics g, String Text, int x, int y)
    {
        if (ZoomManager.GetZoomFactor() >= 1.0) // don't draw strings if shrunk, not readable anyway
        {
            x = ZoomPointX(x);
            y = ZoomPointY(y);

            g.drawString(Text, x, y);
        }
    }

    protected void DrawThickLine(Graphics g, int x1, int y1, int x2, int y2 /* , int thickness */ )
    { // https://www.rgagnon.com/javadetails/java-0260.html, thanks... update: nope, think again

        // This code does not work, the line only has a thickness if drawn
        // absolutely vertically or horizontally...
        /*
        // The thick line is in fact a filled polygon
        int dX = x2 - x1;
        int dY = y2 - y1;
        
        double lineLength = Math.sqrt(dX * dX + dY * dY);
        
        double scale = (double) (thickness) / (2 * lineLength);
        
        // The x, y increments from an end point needed to create a rectangle...
        double ddx = -scale * (double) dY;
        double ddy = +scale * (double) dX;
        ddx += (ddx > 0) ? 0.5 : -0.5;
        ddy += (ddy > 0) ? 0.5 : -0.5;
        int dx = (int)ddx;
        int dy = (int)ddy;
        
        // Now we can compute the corner points...
        int xPoints[] = new int[4];
        int yPoints[] = new int[4];
        
        xPoints[0] = x1 + dx; yPoints[0] = y1 + dy;
        xPoints[1] = x1 - dx; yPoints[1] = y1 - dy;
        xPoints[2] = x2 - dx; yPoints[2] = y2 - dy;
        xPoints[3] = x2 + dx; yPoints[3] = y2 + dy;
        
        g.fillPolygon(xPoints, yPoints, 4);
        */

        // own code: place line ends "in circle" around start/end point,
        // so that there is always a set of lines pointing in the same
        // direction and having the same start/end point distances
        // from each other:

        DrawLine(g, x1, y1, x2, y2);

        int OffsetxLast = Integer.MAX_VALUE;
        int OffsetyLast = Integer.MAX_VALUE;

        for (int r = 0; r < 360; r += 10)
        {
            int Offsetx = (int) Math.sin(Math.toRadians((double) r)) * 1;
            int Offsety = (int) Math.cos(Math.toRadians((double) r)) * 1;

            if (Offsetx != OffsetxLast || Offsety != OffsetyLast)
            {
                DrawLine(g, x1 + Offsetx, y1 + Offsety, x2 + Offsetx, y2 + Offsety);
            }

            OffsetxLast = Offsetx;
            OffsetyLast = Offsety;
        }
    }

    protected void DrawThickRect(Graphics g, int x, int y, int width, int height)
    {
        // DrawThickLine() does the zooming!

        DrawThickLine(g, x, y, x + width, y);
        DrawThickLine(g, x + width, y, x + width, y + height);
        DrawThickLine(g, x + width, y + height, x, y + height);
        DrawThickLine(g, x, y + height, x, y);
    }

    protected void FillRect(Graphics g, int x, int y, int width, int height)
    {
        if (ZoomManager.GetZoomFactor() != 1.0)
        {
            x = ZoomPointX(x);
            y = ZoomPointY(y);
            width = (int) (width * ZoomManager.GetZoomFactor());
            height = (int) (height * ZoomManager.GetZoomFactor());
        }
        g.fillRect(x, y, width, height);
    }

    protected ArrayList<DrawObject> GetChildDrawObjects()
    {
        return new ArrayList<DrawObject>(); // override this if necessary!
    }

    protected long GetCompoundId()
    {
        return CompoundId;
    }

    protected Font GetDefaultFont()
    {
        return DrawStringDefaultFont;
    }

    protected Color GetExcitementColor()
    {
        // fade from yellow to black

        return GetExcitementColor(Color.BLACK);
    }

    protected Color GetExcitementColor(Color BaseColor)
    {
        // fade from yellow to passed base color

        float ExcitementFloat = (float) Math.max(0.0, Math.min(1.0, GetExcitementMaxFromExcitementHistory())); // do NOT use LimitExcitement(), because this is for determining color only!

        int r = (int) (ExcitementFloat * 255.0f);
        int g = (int) (ExcitementFloat * 255.0f);

        if (ExcitementFloat > 0.1f)
            return new Color(r / 255.0f, g / 255.0f, 0.0f);
        else
            return BaseColor;
    }

    protected double GetExcitementMaxFromExcitementHistory()
    {
        double ExcitementMax = 0.0;

        for (int m = 0; m < ExcitementHistoryLength; m++)
            if (ExcitementHistory[m] > ExcitementMax)
                ExcitementMax = ExcitementHistory[m];

        return ExcitementMax;
    }

    protected Font GetHugeFont()
    {
        return DrawStringHugeFont;
    }

    public DrawObject GetParentDrawObject()
    {
        return ParentDrawObject;
    }

    public long GetProperties()
    {
        return Properties;
    }

    protected Font GetSmallFont()
    {
        return DrawStringSmallFont;
    }

    public int GetStrengthMax()
    {
        return StrengthMax;
    }

    public int GetStrengthTrigger()
    {
        return StrengthTrigger;
    }

    public Color GetTransparentColorBlack()
    {
        return TransparentColorBlack;
    }

    public boolean HasParent()
    {
        return (ParentDrawObject != null);
    }

    protected void IncreaseStrength()
    {
        Strength += StrengthIncreaseStep;
        if (Strength > StrengthMax)
            Strength = StrengthMax;
    }

    protected void IncreaseStrengthTrigger()
    {
        StrengthTrigger += 5;
        if (StrengthTrigger > StrengthMax)
            StrengthTrigger = StrengthMax;
    }

    protected void InitializeExcitement(int MillisecondsUntilFullLoss)
    {
        ExcitementCurrent = 0.0; // preset

        int TickLengthInMilliseconds = (1000 / Simulation.GetTicksPerSecond());

        ExcitementLossPerTick = 1.0 * ((double) TickLengthInMilliseconds / (double) MillisecondsUntilFullLoss);
    }

    protected boolean IsActivated()
    {
        return DoesActivate(ExcitementCurrent);
    }

    protected boolean IsPartOfMultiSelection()
    {
        return IsPartOfMultiSelection;
    }

    public boolean IsReducedDrawOrderEnabled()
    {
        return ReducedDrawOrderEnabled;
    }

    protected boolean IsThereNoteworthyExcitement()
    {
        return (ExcitementCurrent > 0.1);
    }

    @SuppressWarnings("static-access")
    protected boolean IsWithinWindowBounds(DrawObject DrawObject, int RecursionCounter)
    {
        if (RecursionCounter >= 10) // just to make sure... :-P
        {
            System.out.println("internal error: IsWithinWindowBounds() stuck in recursion loop (probably)!");
            return false;
        }

        if (DrawObject == null) // should not happen
            return false;

        // NOTE: call UpdateMainWindowSizeInfo() whenever necessary to make out-of-window-bounds check work!
        // Manipulate UpdateMainWindowSizeInfo() to test out-of-window-bounds check (verified on 2022-07-24_15-59).

        if (DrawObject.IsCommentField)
        {
            // ALWAYS draw parts of any CommentField, to avoid that its "frame Lines"
            // are hidden because their nodes are out of the window area (see comment below);
            // We can assume there is a limited number of CommentFields, not thousands, like
            // Neurons or VisualFieldPixels, so that should not have a notable impact on
            // drawing speed.

            return true;
        }

        if (DrawObject.IsLine())
        {
            // do not check DrawObject.Pos and .Size, it's not useful for Line!
            if (IsWithinWindowBounds(((Line) DrawObject).GetStartDrawObject(), RecursionCounter + 1) || IsWithinWindowBounds(((Line) DrawObject).GetEndDrawObject(), RecursionCounter + 1))
            {
                return true;
            }
            else
            {
                // NOTE: if the user draws Lines which go over large distances
                // in a "gross" way, some Lines might not be drawn although they
                // should be visible. We currently accept this fault, maybe
                // fix this later... (or tell the user not to build nonsense).

                /*
                // Lines (probably) ACROSS visible screen area?
                // DOES ALSO NOT WORK COMPLETELY as further attached, (short)
                // Lines may get hidden so that the screen-crossing Lines
                // are not drawn, too!
                if (((Line) DrawObject).GetStartDrawObject().Pos.x < 0 &&
                		((Line) DrawObject).GetEndDrawObject().Pos.x > 0)
                {
                	if (((Line) DrawObject).GetStartDrawObject().Pos.y < 0 &&
                			((Line) DrawObject).GetEndDrawObject().Pos.y > 0)
                		return true;
                	if (((Line) DrawObject).GetEndDrawObject().Pos.y < 0 &&
                			((Line) DrawObject).GetStartDrawObject().Pos.y > 0)
                		return true;
                }
                if (((Line) DrawObject).GetEndDrawObject().Pos.x < 0 &&
                		((Line) DrawObject).GetStartDrawObject().Pos.x > 0)
                {
                	if (((Line) DrawObject).GetStartDrawObject().Pos.y < 0 &&
                			((Line) DrawObject).GetEndDrawObject().Pos.y > 0)
                		return true;
                	if (((Line) DrawObject).GetEndDrawObject().Pos.y < 0 &&
                			((Line) DrawObject).GetStartDrawObject().Pos.y > 0)
                		return true;
                }
                */
            }
        }
        else
        {
            // ATTENTION: *DrawObject*.Pos/Size!
            // ATTENTION: these are the ORIGINAL positions and sizes stored in memory,
            // THEY ARE NOT AFFECTED BY THE ZOOMFACTOR! We just convert the original
            // coordinates like they were the drawn ones, so that they become SCREEN
            // coordinates (regarding ZoomFactor) - then the rest of the check is trivial.
            // 2022-07-31_12-15: the following within-bounds-check with much precomputation
            // was verified by narrowing the draw area, it worked.
            // ->
            if ((DrawObject.Pos.x + DrawObject.Size.x) >= ZoomManager.GetNegZoomShiftXDivZoomFactor() && (DrawObject.Pos.y + DrawObject.Size.y) >= ZoomManager.GetNegZoomShiftYDivZoomFactor() && // x >= -y is the same as x + y >= 0 (maybe faster)!
                (DrawObject.Pos.x) < ZoomManager.GetDrawWidthMinusZoomShiftXDivZoomFactor() && (DrawObject.Pos.y) < ZoomManager.GetDrawHeightMinusZoomShiftYDivZoomFactor())
            {
                return true;
            }
        }

        return false;
    }

    protected boolean IsZombie()
    {
        return IsZombie;
    }

    protected double LimitExcitement(double ExcitementCurrent)
    {
        if (ExcitementCurrent < 0.0)
            ExcitementCurrent = 0.0;
        if (ExcitementCurrent > +1.0)
            ExcitementCurrent = +1.0;

        return ExcitementCurrent;
    }

    protected double LimitExcitementAllowNegative(double ExcitementCurrent)
    {
        // allow negative excitement because neuron input could be inhibiting

        if (ExcitementCurrent < -1.0)
            ExcitementCurrent = -1.0;
        if (ExcitementCurrent > +1.0)
            ExcitementCurrent = +1.0;

        return ExcitementCurrent;
    }

    protected void LoseExcitementAndAddExcitementForNextTick()
    {
        // NOTE: without ExcitementAddForNextTick, Lines in a succession got all
        // activated at once, depending on their order in the DrawObjects array!

        // validity (i.e. could appear in the real brain): the excitement shortly gets a spike...
        ExcitementCurrent += ExcitementAddForNextTick;

        ShiftExcitementHistory(ExcitementCurrent);

        // validity: ...and then "collapses" again to basic level
        ExcitementCurrent -= ExcitementLossPerTick;

        // verify and reset
        ExcitementCurrent = LimitExcitement(ExcitementCurrent);
        ExcitementAddForNextTick = 0.0; // reset for next usage
    }

    protected void MarkAsZombie()
    {
        IsZombie = true;
    }

    protected void Move(Point MoveAmount)
    {
        Pos.x += MoveAmount.x;
        Pos.y += MoveAmount.y;
    }

    protected void NegativeDrawRect(Graphics g, int x, int y, int width, int height)
    {
        // https://stackoverflow.com/questions/4213040/negative-number-in-fillrect/

        if (width < 0)
            x -= Math.abs(width);
        if (height < 0)
            y -= Math.abs(height);

        DrawRect(g, x, y, Math.abs(width), Math.abs(height));
    }

    public void ResetState()
    { // meant to be called for EVERY DrawObject before beginning a new simulation

        if (IsInLearningNeuronState) // ELSE Strength was set BY USER, DO NOT RESET it!
        {
            Strength = 0;
            // DO NOT reset StrengthMax, it's a fixed setting for some DrawObjects!
        }
        for (int m = 0; m < HormoneStrengths.size(); m++)
            HormoneStrengths.set(m, 0.0);

        ExcitementCurrent = 0.0;
        ExcitementAddForNextTick = 0.0;

        ExcitementPartnerServedInThisTick = false;

        for (int m = 0; m < ExcitementHistoryLength; m++)
            ExcitementHistory[m] = 0.0;
        ExcitementHistoryWriteIndex = 0;

        if (this instanceof JavaCADU)
            ((JavaCADU) this).ResetJavaCADU();
    }

    protected void SetColor(Graphics g, Color ColorNew)
    {
        g.setColor(ColorNew);

        /* // not faster, practically the same CPU time required for a tick of blinking_huge.ncide
        if (ColorNew != ColorLast)
        {
        	g.setColor(ColorNew);
        	ColorLast = ColorNew;
        }
        */
    }

    protected void SetCompoundId(long CompoundIdNew)
    {
        if (CompoundIdNew >= 0 || CompoundIdNew == ID_UNSET)
            CompoundId = CompoundIdNew;
        else
            System.out.println("error in DrawObject.SetCompoundId(): passed value not useful!");
    }

    protected void SetHighlighted(boolean IsHighlightedNew)
    {
        IsHighlighted = IsHighlightedNew;
    }

    public void SetIsCommentField(boolean IsCommentFieldNew)
    {
        IsCommentField = IsCommentFieldNew;
    }

    public void SetIsLearningNeuron(boolean IsInLearningNeuronStateNew)
    {
        IsInLearningNeuronState = IsInLearningNeuronStateNew;
    }

    public void SetParentDrawObject(DrawObject ParentDrawObjectNew)
    {
        ParentDrawObject = ParentDrawObjectNew;
    }

    protected void SetPartOfMultiSelection(boolean IsPartNew)
    {
        IsPartOfMultiSelection = IsPartNew;
    }

    protected void SetPos(Point PosNew)
    {
        Pos = AlignOnGrid(PosNew);
    }

    protected void SetPosNoAlignOnGrid(Point PosNew)
    {
        Pos = PosNew;
    }

    protected void SetSize(Point SizeNew)
    {
        Size = SizeNew;
    }

    protected void SetStrength(int StrengthNew)
    {
        if (StrengthNew >= 0 && StrengthNew <= StrengthMax)
            Strength = StrengthNew;
        else
            System.out.println("error in DrawObject.SetStrength(): passed value invalid!");
    }

    protected void SetStrengthTrigger(int StrengthTriggerNew)
    {
        if (StrengthTriggerNew >= 0 && StrengthTriggerNew <= StrengthMax)
            StrengthTrigger = StrengthTriggerNew;
        else
            System.out.println("error in DrawObject.SetStrengthTrigger(): passed value invalid!");
    }

    protected void ShiftExcitementHistory(double ExcitementToAdd)
    {
        ExcitementHistory[ExcitementHistoryWriteIndex] = ExcitementToAdd;
        ExcitementHistoryWriteIndex++;
        if (ExcitementHistoryWriteIndex >= ExcitementHistoryLength)
            ExcitementHistoryWriteIndex = 0;
    }

    protected void StateFromFile(DataInputStream is) throws IOException
    {
        @SuppressWarnings("unused")
        short FileFormatVersionRead;

        FileFormatVersionRead = is.readShort(); // it might be necessary to skip writing or reading certain values to be "in sync", depending on version of the saved .ncide file

        if (FileFormatVersionRead >= 3)
            Properties = is.readLong();
        Pos.x = is.readInt();
        Pos.y = is.readInt();
        Size.x = is.readInt();
        Size.y = is.readInt();
        IsHighlighted = is.readBoolean() & false; // do not read this, it's not useful to be reloaded (as it is rather a temporary state)
        IsZombie = is.readBoolean();
        IsPartOfMultiSelection = is.readBoolean() & false; // do not read this, it's not useful to be reloaded (as it is rather a temporary state)
        CompoundId = is.readLong();
        Text = Tools.ReadUTF32Bit(is);
        StrengthMax = is.readInt();
        Strength = is.readInt();
        StrengthTrigger = is.readInt();
        IsNegative = is.readBoolean();
        IsInLearningNeuronState = is.readBoolean();
        IsCommentField = is.readBoolean();
        IsEnabled = is.readBoolean();

        int HormoneCount = is.readInt();

        if (HormoneNames == EmptyHormoneNames)
            HormoneNames = new ArrayList<String>();
        else
            HormoneNames.clear();

        if (HormoneStrengths == EmptyHormoneStrengths)
            HormoneStrengths = new ArrayList<Double>();
        else
            HormoneStrengths.clear();

        if (HormoneCount > 0)
        {
            HormoneNames.ensureCapacity(HormoneCount); // not sure if this is effective
            HormoneStrengths.ensureCapacity(HormoneCount); // not sure if this is effective
            for (int m = 0; m < HormoneCount; m++)
            {
                HormoneNames.add(is.readUTF());
                HormoneStrengths.add(is.readDouble());
            }
        }

        if (FileFormatVersionRead < 4)
        {
            if ((this instanceof TextField)) // a TextField with IsCommentField flag set is also enabled (although it is not meant to be excited, although it can), for conformity and to simplify code (especially drawing TextField)
                IsEnabled = true;
        }
    }

    protected void StateToFile(DataOutputStream os) throws IOException
    {
        os.writeShort(FileFormatVersion);
        os.writeLong(Properties);
        os.writeInt(Pos.x);
        os.writeInt(Pos.y);
        os.writeInt(Size.x);
        os.writeInt(Size.y);
        os.writeBoolean(IsHighlighted & false);
        os.writeBoolean(IsZombie);
        os.writeBoolean(IsPartOfMultiSelection & false);
        os.writeLong(CompoundId);
        Tools.WriteUTF32Bit(os, Text);
        os.writeInt(StrengthMax);
        os.writeInt(Strength);
        os.writeInt(StrengthTrigger);
        os.writeBoolean(IsNegative);
        os.writeBoolean(IsInLearningNeuronState);
        os.writeBoolean(IsCommentField);
        os.writeBoolean(IsEnabled);

        os.writeInt(HormoneNames.size()); // should always be the same as HormoneStrengths.size()!
        for (int m = 0; m < HormoneNames.size(); m++)
        {
            os.writeUTF(HormoneNames.get(m));
            os.writeDouble(HormoneStrengths.get(m));
        }
    }

    public void ToggleIsInLearningNeuronState()
    {
        IsInLearningNeuronState = !IsInLearningNeuronState;
    }
}
